<?php
/**
 *  NevuxBB - Free Forum Software
 *  Copyright (C) 2008, 2010 NevuxBB (No one specific author)*  This program is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 * ACP Update
 * File: update.php
 * Purpose: For Updating the forum
 * Todo: Execute SQL Queries
 *
 */

$acp = true;
require('../system/files/global.php');

// must be a valid session otherwise we wouldn't be here
// display the header data
$core_template_system->setvar(array('acp_page_title' => 'Update',
									'tab' => 'update',
									'show_full_header' => true));

// load the header
$core_template_system->pparse();

// load and display the main template body
$core_template_system->newTemplate(BASE_PATH . '/acp/template/main.html');
$core_template_system->pparse();

// load the updating system's core
require(BASE_PATH . '/system/classes/update_class.php');
$update = new UpdateSystem;

// what operation do we need to do?
switch($_GET['act']) {
	case 'settings':
		// settings
		$core_template_system->newTemplate(BASE_PATH . '/acp/template/update_settings.html');
		
		// are we to update with a new default?
		if($_GET['sub'] == 'mkdef' && is_numeric($_GET['content'])) {
			// set new default
			// does the one the user has selected even exist?
			$update_item = Db_Abstraction::query("SELECT `upd_id` FROM `nbb_settings_update` WHERE `upd_id` = '" . Db_Abstraction::clean($_GET['content']) . "' LIMIT 1;");
			
			// did it return 0 results?
			if(mysql_num_rows($update_item) == 0) {
				// failed, doesn't exist
				#error page
				$core_template_system->setvar(array('setting_new_default' => true,
													'new_default_failed'  => true,
													'information'		  => 'Failed to find the selected update server!'));
			} else {
				// does exist
				// unset all defaults first
				Db_Abstraction::query("UPDATE `nbb_settings_update` SET `upd_default` = 0 WHERE `upd_default` = 1;");
				
				// set our new one as default
				Db_Abstraction::query("UPDATE `nbb_settings_update` SET `upd_default` = 1 WHERE `upd_id` = '" . Db_Abstraction::clean($_GET['content']) . "' LIMIT 1;");
			
				// was a success, show this to the user
				$core_template_system->setvar(array('setting_new_default' => true,
													'information'		  => 'Successfully changed default update server!'));
			}
		} else {
			// gain a list of available servers shipped with this package
			$queryr = Db_Abstraction::query("SELECT * FROM `nbb_settings_update`");
			
			// build an array of this data
			$servers_list = array();
			while($row = Db_Abstraction::fetch_assoc($queryr)) {
				// array contents
				$servers_list[] = array('id'		 => $row['upd_id'],
										'name'		 => $row['upd_name'],
										'address'	 => $row['upd_address'],
										'location'	 => $row['upd_location'],
										'icon'		 => $row['upd_icon'],
										'is_default' => $row['upd_default']);
			}
			
			// set array as template loop
			$core_template_system->setloop('serverlist', $servers_list);
		}
		
		// end
		break;
	case 'do_update':
		// update
		$core_template_system->newTemplate(BASE_PATH . '/acp/template/update_forum.html');
		
		// are we actually attempting to update here?
		if(isset($_GET['to']) && is_numeric($_GET['to'])) {
			// is valid then
			// is this version equal to or less than our current version?
			if($_GET['to'] <= $acp_reserved_settings['forum_version']['data']) {
				// can't do that kind of upgrade
				$core_template_system->setvar(array('update_failed' => true,
													'error' => 'Downgrading is not permitted'));
			} else {
				// start the buffer
				ob_start();
				
				// good, version is newer than our current one
				$do_not_run_parser = true;
				$core_template_system->pparse();
				
				// creating second part of the table
				$update->newItem('Begining the update class');
				
				// we need to recheck this number, is it plus one above our current?
				if($_GET['to'] != $acp_reserved_settings['forum_version']['data'] + 1) {
					$update->newItem('You can\'t jump ahead, you need to update in ascending numeric order', true);
				} else {
					// start session with server
					$update->newItem('Attempting to communicate with update server');
					
					// give class the future version number
					$update->updateToData($_GET['to']);
					
					// execute the request
					$curr_info = $update->execute_request('future_version');
		
					// did it work?
					if($curr_info === false || $curr_info === NULL) {
						// failed
						$update->newItem('Server returned results that were either not valid or none at all', true);
					} else {
						// did work, check the results
						$update->newItem('Server returned some results, starting the XML parsing session');
						
						// attempt to parse these results
						try {
							$xml = new SimpleXMLElement($curr_info);
						} catch (Exception $e) {
							// update server produced invalid XML code
							trigger_error($e, E_USER_WARNING);
							
							// fatal error, update server produced bizzare xml code
							$update->newItem('Server returned invalid XML code, try using a different update server or try again later!', true);
							$failed = true;
						}
						
						if(!$failed) {
							// continue with the processing, did the server internally error?
							if(isset($xml->error)) {
								// server produced an error
								$update->newItem('Server claims the request failed, the error returned by the server was: <i>' . $xml->error . '</i>', true);
							} else {
								// fine to proceed
								// does the info the server sent back match what we're wanting to do?
								if($xml['reference'] == $_GET['to']) {
									// matches, proceed
									$update->newItem('Extracting changed files data');
									
									// decode and unserialise changed files object
									$new_data = unserialize(base64_decode($xml->changedfiles));
									
									// check the md5 of each file, to make sure it isn't already the current version
									$update->newItem('Begining to run through each file the update server sent...');
									
									// this array will store all failed backups
									$failed_backups = array(); 
									$i = 0;
									
									// check
									foreach($new_data as $key) {
										$curr_file_md5 = md5_file(BASE_PATH . '/' . $key['file_name']);
										
										// does the md5 match?
										if($curr_file_md5 == $key['md5_sum']) {
											// does match, already up to date
											$update->newItem('File: <em>' . $key['file_name'] . '</em> is already up to date! Not updating... Moving on...');
										} else {
											// doesn't match, rewrite it
											// make a backup copy first
											$update->newItem('Attempting to create backup of file: <em>' . $key['file_name'] . '</em>');
											
											// call the kernel
											if(KernelBaseSystem::copyFile(BASE_PATH . '/' . $key['file_name'], $key['file_name'] . '.BAK.php')) {
												// copied file successfully
												$update->newItem('A copy of <em>' . $key['file_name'] . '</em> has been made...');
												
												// we need to rewrite this file with the new contents
												// download contents of new file from the server
												$contents = $update->GetContents($key['server_loc']);
												
												if($contents === false) {
													// update server was wrong or can't be connected to
													$update->newItem('Failed to get the contents of <em>' . $key['file_name'] . '</em> from the remote server...', true);
													$failed_backups[$i] = $key['file_name'];
													$i++;
												} else {
													// worked, write this content
													if(KernelBaseSystem::writeFile(BASE_PATH . '/' . $key['file_name'], $contents)) {
														// wrote new file
														$update->newItem('The writing of file ' . $key['file_name'] . ' was successful!');
														
														// does it pass the MD5 test?
														if(md5_file(BASE_PATH . '/' . $key['file_name']) === $key['md5_sum']) {
															// passed the MD5 test, completed
															$update->newItem('CHECKSUM of file ' . $key['file_name'] . ' matched what the server sent, done!');
															
															// remove backup file
															if(KernelBaseSystem::removeFile(BASE_PATH . '/' . $key['file_name'] . '.BAK.php')) {
																// removed file, all good
																$update->newItem('Removed the backup file of ' . $key['file_name'] . '!');
															} else {
																// couldn't remove file?
																$update->newItem('Unable to remove the backup file of ' . $key['file_name'] . ', you may need to do this manually .. it\'s called: <em>' . $key['file_name'] . '.BAK.php</em>', true);
															}
														} else {
															// MD5 not the same as remote server, restore backup file
															$update->newItem('CHECKSUM OF FILE ' . $key['file_name'] . ' DID NOT MATCH!', true);
															
															// we need to remove the current one and then rename the backup one
															if(KernelBaseSystem::removeFile(BASE_PATH . '/' . $key['file_name'])) {
																// removed successfully
																$update->newItem('Removed the file ' . $key['file_name'] . ', attempting to rename backup file to this so it can be restored...');
																
																// rename the file
																if(KernelBaseSystem::renameFile(BASE_PATH . '/' . $key['file_name'] . '.BAK.php', BASE_PATH . '/' . $key['file_name'], true)) {
																	// worked, restored
																	$update->newItem('The file ' . $key['file_name'] . ', was restored without problems, try a different update server and try again!');
																} else {
																	// didn't work, the user needs to restore this
																	$update->newItem('Unable to restore the file ' . $key['file_name'] . ', you will need to rename <em>' . $key['file_name'] . '.BAK.php</em> to <em>' . $key['file_name'] . '</em>!', false);
																}
															} else {
																// can't be removed, then, more than likely, can't be written to
																$update->newItem('Unable to remove the file ' . $key['file_name'] . ', you will need to remove this file manually and rename <em>' . $key['file_name'] . '.BAK.php</em> to <em>' . $key['file_name'] . '</em>!', false);
															}
														}
													} else {
														// failed to write new file
														$update->newItem('Failed to write the new contents of <em>' . $key['file_name'] . '</em>...', true);
														$failed_backups[$i] = $key['file_name'];
														$i++;
													}
												}
											} else {
												// failed, can't copy
												$update->newItem('Failed to backup <em>' . $key['file_name'] . '</em>...', true);
												$failed_backups[$i] = $key['file_name'];
												
												// increment i
												$i++;
											}
										}
									}
									
									// were there any errors?
									if(count($failed_backups) == 0) {
										// success, no errors occured
										$update->newItem('UPDATE WAS SUCCESSFUL', 'ss');
										
										// update forum with new version
										Db_Abstraction::query("UPDATE `nbb_settings` SET `settings_data` = '" . Db_Abstraction::clean($_GET['to']) . "' WHERE `settings_name` = 'forum_version'");
									} else {
										// some errors, show these
										$update->newItem('Some errors were encountered when upgrading, please fix these problems and run the update system again...', false);
									}
								} else {
									// didn't match, update server is confused
									$update->newItem('The update server appears to be confused, it sent back a different version than what we are wanting to upgrade to.', true);
								}
							}
						}
					}
				}
				
				// end the table to create a valid table
				echo('</table>');
			}
		} else {
			// no version to update to
			$core_template_system->setvar(array('update_failed' => true,
												'error' => 'No future version selected, upgrade has been cancelled'));
		}
		break;
	default:
		// load standard page
		$core_template_system->newTemplate(BASE_PATH . '/acp/template/update_main.html');
		
		// send our current version to the update system
		$update->curr_version($acp_reserved_settings['forum_version']['data']);
		$curr_info = $update->execute_request('curr');
		
		// did it work?
		if($curr_info === false || $curr_info === NULL) {
			// no
			$core_template_system->setvar(array('update_failed' => true,
												'error' => 'Update server unavailable! - Try selecting a different update server to cure the problem!'));
		} else {
			// did work, good job .. now parse it
			try {
				$xml = new SimpleXMLElement($curr_info);
			} catch (Exception $e) {
				// update server produced invalid XML code
				trigger_error($e, E_USER_WARNING);
				
				// send this error to the acp
				$core_template_system->setvar(array('update_failed' => true,
													'error' => 'Update server produced code that was unreadable, perhaps the update server has become corrupt?'));
			}
			
			if(isset($xml->error)) {
				// update server error
				$core_template_system->setvar(array('update_failed' => true,
													'error' => $xml->error));
			} else {
				// fine
				if($xml['reference'] == $acp_reserved_settings['forum_version']['data']) {
					// update server sent back the same id, which is good
					$core_template_system->setvar(array('version' => $xml['reference'],
														'date'    => $xml->date,
														'info'    => $xml->info));
					
					// what's the latest version though?
					if($xml->latestversion['reference'] == $acp_reserved_settings['forum_version']['data']) {
						// you have the latest version
						$core_template_system->setvar(array('have_latest' => true));
					} else {
						// new version available
						$core_template_system->setvar(array('lversion' => $xml->latestversion['reference'],
															'ldate'    => $xml->latestversion->date,
															'linfo'    => $xml->latestversion->info));
						
						// we need to list the changed files, a base64 encoded string
						$filesc = unserialize(base64_decode($xml->latestversion->changedfiles));
						
						// set this as an official loop
						$core_template_system->setloop('files_changed', $filesc);
					}
				} else {
					// confusing, update server sent back a different ID?
					$core_template_system->setvar(array('update_failed' => true,
														'error' => 'Update server failed to correctly identify your version sending back a different ID!'));
				}
			}
		}
		
		// break
		break;
}

// display the content
if(!$do_not_run_parser) {
	// if update is in progress, this
	// would have been called earlier
	$core_template_system->pparse();
}

// reset items
$core_template_system->reset();

// load the footer
$core_template_system->newTemplate(BASE_PATH . '/acp/template/footer.html');
$core_template_system->pparse();
?>